/*
 * Copyright 2014, General Dynamics C4 Systems
 *
 * SPDX-License-Identifier: GPL-2.0-only
 */

#include <kernel/thread.h>
#include <api/failures.h>
#include <api/syscall.h>
#include <machine/io.h>
#include <arch/object/ioport.h>
#include <arch/api/invocation.h>
#include <plat/machine/io.h>
/*Z 用pattern的1位掩码*w，set为真时置上掩码，为假时清除掩码位 */
static inline void apply_pattern(word_t_may_alias *w, word_t pattern, bool_t set)
{
    if (set) {
        *w |= pattern;
    } else {
        *w &= ~pattern;
    }
}
/*Z 做一个字：位start~end-1均为1，其它位为0 */
static inline word_t make_pattern(int start, int end)
{
    // number of bits we want to have set
    int num_bits = end - start;
    // shift down to cut off the bits we don't want, then shift up to put the
    // bits into position
    return (~(word_t)0) >> (CONFIG_WORD_SIZE - num_bits) << start;
}
/*Z 验证自指定端口开始size大小的端口均位于能力范围内 */
static exception_t ensurePortOperationAllowed(cap_t cap, uint32_t start_port, uint32_t size)
{
    uint32_t first_allowed = cap_io_port_cap_get_capIOPortFirstPort(cap);
    uint32_t last_allowed = cap_io_port_cap_get_capIOPortLastPort(cap);
    uint32_t end_port = start_port + size - 1;
    assert(first_allowed <= last_allowed);
    assert(start_port <= end_port);

    if ((start_port < first_allowed) || (end_port > last_allowed)) {
        userError("IOPort: Ports %d--%d fall outside permitted range %d--%d.",
                  (int)start_port, (int)end_port,
                  (int)first_allowed, (int)last_allowed);
        current_syscall_error.type = seL4_IllegalOperation;
        return EXCEPTION_SYSCALL_ERROR;
    }

    return EXCEPTION_NONE;
}
/*Z 清除已分配I/O端口位图全局变量中，指定范围的I/O端口位 */
void freeIOPortRange(uint16_t first_port, uint16_t last_port)
{               /*Z 已分配的I/O端口位图 */
    setIOPortMask(x86KSAllocatedIOPorts, first_port, last_port, false);
}
/*Z 指定端口范围是否全部未分配 */
static bool_t isIOPortRangeFree(uint16_t first_port, uint16_t last_port)
{
    int low_word = first_port >> wordRadix;
    int high_word = last_port >> wordRadix;
    int low_index = first_port & MASK(wordRadix);
    int high_index = last_port & MASK(wordRadix);

    // check if we are operating on a partial word
    if (low_word == high_word) {
        if ((x86KSAllocatedIOPorts[low_word] & make_pattern(low_index, high_index + 1)) != 0) {
            return false;
        }
        return true;
    }
    // check the starting word
    if ((x86KSAllocatedIOPorts[low_word] & make_pattern(low_index, CONFIG_WORD_SIZE)) != 0) {
        return false;
    }
    low_word++;
    // check the rest of the whole words
    while (low_word < high_word) {
        if (x86KSAllocatedIOPorts[low_word] != 0) {
            return false;
        }
        low_word++;
    }
    // check any trailing bits
    if ((x86KSAllocatedIOPorts[low_word] & make_pattern(0, high_index + 1)) != 0) {
        return false;
    }
    return true;
}
/*Z 标记全局I/O端口位图中的指定范围已分配，新建I/O端口访问能力并与控制能力建立关联 */
static exception_t invokeX86PortControl(uint16_t first_port, uint16_t last_port, cte_t *ioportSlot, cte_t *controlSlot)
{
    setIOPortMask(x86KSAllocatedIOPorts, first_port, last_port, true);
    cteInsert(cap_io_port_cap_new(first_port, last_port
#ifdef CONFIG_VTX
                                  , VPID_INVALID
#endif
                                 ),
              controlSlot, ioportSlot);

    return EXCEPTION_NONE;
}
/*Z 引用cap_io_port_control_cap能力的系统调用：为CNode分配空闲端口 */
exception_t decodeX86PortControlInvocation(/*Z 系统调用传入的当前线程参数： */
    word_t invLabel,        /*Z 消息标签(错误类型) */
    word_t length,          /*Z 消息长度 */
    cptr_t cptr,            /*Z CSlot句柄 */
    cte_t *slot,            /*Z 其CSlot */
    cap_t cap,              /*Z 其能力 */
    extra_caps_t excaps,    /*Z 额外能力 */
    word_t *buffer          /*Z IPC buffer */
)
{
    uint16_t first_port;
    uint16_t last_port;
    word_t index, depth;
    cap_t cnodeCap;
    cte_t *destSlot;
    lookupSlot_ret_t lu_ret;
    exception_t status;

    if (invLabel != X86IOPortControlIssue) {
        userError("IOPortControl: Unknown operation.");
        current_syscall_error.type = seL4_IllegalOperation;
        return EXCEPTION_SYSCALL_ERROR;
    }

    if (length < 4 || excaps.excaprefs[0] == NULL) {
        userError("IOPortControl: Truncated message.");
        current_syscall_error.type = seL4_TruncatedMessage;
        return EXCEPTION_SYSCALL_ERROR;
    }

    first_port = getSyscallArg(0, buffer) & 0xffff;/*Z ---------------------------消息传参：0-第1个端口号 */
    last_port = getSyscallArg(1, buffer) & 0xffff;                                      /*Z 1-最后一个端口号 */
    index = getSyscallArg(2, buffer);                                                   /*Z 2-控制端口访问的能力句柄 */
    depth = getSyscallArg(3, buffer);                                                   /*Z 3-其深度 */

    cnodeCap = excaps.excaprefs[0]->cap;                                                /*Z extraCaps0-目标CNode */

    if (last_port < first_port) {/*Z 验证端口参数有效性 */
        userError("IOPortControl: Last port must be > first port.");
        current_syscall_error.type = seL4_InvalidArgument;
        current_syscall_error.invalidArgumentNumber = 1;
        return EXCEPTION_SYSCALL_ERROR;
    }

    if (!isIOPortRangeFree(first_port, last_port)) {/*Z 验证是否有端口已被分配 */
        userError("IOPortControl: Some ports in range already in use.");
        current_syscall_error.type = seL4_RevokeFirst;
        return EXCEPTION_SYSCALL_ERROR;
    }

    lu_ret = lookupTargetSlot(cnodeCap, index, depth);
    if (lu_ret.status != EXCEPTION_NONE) {/*Z 查找目标CSlot */
        userError("Target slot for new IO Port cap invalid: cap %lu.", getExtraCPtr(buffer, 0));
        return lu_ret.status;
    }
    destSlot = lu_ret.slot;

    status = ensureEmptySlot(destSlot);
    if (status != EXCEPTION_NONE) {/*Z 验证目标CSlot是否为空 */
        userError("Target slot for new IO Port cap not empty: cap %lu.", getExtraCPtr(buffer, 0));
        return status;
    }

    setThreadState(NODE_STATE(ksCurThread), ThreadState_Restart);
    return invokeX86PortControl(first_port, last_port, destSlot, slot);/*Z 标记全局I/O端口位图中的指定范围已分配，新建I/O端口访问能力并与控制能力建立关联 */
}
/*Z 按invLabel指示的大小读端口。如果是Call类调用，通过IPC给当前线程发送结果 */
static exception_t invokeX86PortIn(word_t invLabel, uint16_t port, bool_t call)
{
    uint32_t res;
    word_t len;

    switch (invLabel) {
    case X86IOPortIn8:
        res = in8(port);
        break;
    case X86IOPortIn16:
        res = in16(port);
        break;
    case X86IOPortIn32:
        res = in32(port);
        break;
    }

    if (call) {
        setRegister(NODE_STATE(ksCurThread), badgeRegister, 0);

        if (n_msgRegisters < 1) {
            word_t *ipcBuffer;
            ipcBuffer = lookupIPCBuffer(true, NODE_STATE(ksCurThread));
            if (ipcBuffer != NULL) {
                ipcBuffer[1] = res;
                len = 1;
            } else {
                len = 0;
            }
        } else {
            setRegister(NODE_STATE(ksCurThread), msgRegisters[0], res);
            len = 1;
        }

        setRegister(NODE_STATE(ksCurThread), msgInfoRegister,
                    wordFromMessageInfo(seL4_MessageInfo_new(0, 0, 0, len)));
    }
    // Prevent handleInvocation from attempting to complete the 'call' with an empty
    // message (via replyFromKernel_success_empty) by forcing the thread state to
    // be running. This prevents our stored message we just created from being
    // overwritten.
    setThreadState(NODE_STATE(ksCurThread), ThreadState_Running);

    return EXCEPTION_NONE;
}
/*Z 按invLabel指示的大小写端口 */
static exception_t invokeX86PortOut(word_t invLabel, uint16_t port, uint32_t data)
{
    switch (invLabel) {
    case X86IOPortOut8:
        out8(port, data);
        break;
    case X86IOPortOut16:
        out16(port, data);
        break;
    case X86IOPortOut32:
        out32(port, data);
        break;
    }

    return EXCEPTION_NONE;
}
/*Z 引用cap_io_port_cap能力的系统调用：I/O端口输入、输出*/
exception_t decodeX86PortInvocation(
    word_t invLabel,        /*Z 消息标签(错误类型) */
    word_t length,          /*Z 消息长度 */
    cptr_t cptr,            /*Z CSlot句柄 */
    cte_t *slot,            /*Z 其CSlot */
    cap_t cap,              /*Z 其能力 */
    extra_caps_t excaps,    /*Z 额外能力 */
    bool_t call,            /*Z 是否Call调用 */
    word_t *buffer          /*Z IPC buffer */
)
{
    exception_t ret;

    if (invLabel == X86IOPortIn8 || invLabel == X86IOPortIn16 || invLabel == X86IOPortIn32) {/*Z ---------------子功能：I/O端口输入 */
        if (length < 1) {
            userError("IOPort: Truncated message.");
            current_syscall_error.type = seL4_TruncatedMessage;
            return EXCEPTION_SYSCALL_ERROR;
        }
        /* Get the port the user is trying to read from. */
        uint16_t port = getSyscallArg(0, buffer) & 0xffff;  /*Z -----------------------消息传参：0-端口号 */
        switch (invLabel) {/*Z 验证端口在能力范围内 */
        case X86IOPortIn8:
            ret = ensurePortOperationAllowed(cap, port, 1);
            break;
        case X86IOPortIn16:
            ret = ensurePortOperationAllowed(cap, port, 2);
            break;
        case X86IOPortIn32:
            ret = ensurePortOperationAllowed(cap, port, 4);
            break;
        }
        if (ret != EXCEPTION_NONE) {
            return ret;
        }
        setThreadState(NODE_STATE(ksCurThread), ThreadState_Restart);
        return invokeX86PortIn(invLabel, port, call);/*Z 读端口。如果是Call类调用，通过IPC给当前线程发送结果 */
    } else if (invLabel == X86IOPortOut8 || invLabel == X86IOPortOut16 || invLabel == X86IOPortOut32) {/*Z ---------------子功能：I/O端口输出 */
        /* Ensure the incoming message is long enough for the write. */
        if (length < 2) {
            userError("IOPort Out: Truncated message.");
            current_syscall_error.type = seL4_TruncatedMessage;
            return EXCEPTION_SYSCALL_ERROR;
        }
        /* Get the port the user is trying to write to. */
        uint16_t port = getSyscallArg(0, buffer) & 0xffff;/*Z ------------------------消息传参：0-端口号 */
        seL4_Word raw_data = getSyscallArg(1, buffer);                                      /*Z 1-要输出的值 */
        /* We construct the value for data from raw_data based on the actual size of the port
           operation. This ensures that there is no 'random' user data left over in the value
           passed to invokeX86PortOut. Whilst invokeX86PortOut will ignore any extra data and
           cast down to the correct word size removing the extra here is currently relied upon
           for verification */
        uint32_t data;

        switch (invLabel) {
        case X86IOPortOut8:
            ret = ensurePortOperationAllowed(cap, port, 1);
            data = raw_data & 0xff;
            break;
        case X86IOPortOut16:
            ret = ensurePortOperationAllowed(cap, port, 2);
            data = raw_data & 0xffff;
            break;
        case X86IOPortOut32:
            ret = ensurePortOperationAllowed(cap, port, 4);
            data = raw_data & 0xffffffff;
            break;
        }
        if (ret != EXCEPTION_NONE) {
            return ret;
        }
        setThreadState(NODE_STATE(ksCurThread), ThreadState_Restart);
        return invokeX86PortOut(invLabel, port, data);/*Z 按invLabel指示的大小写端口 */
    } else {
        userError("IOPort: Unknown operation.");
        current_syscall_error.type = seL4_IllegalOperation;
        return EXCEPTION_SYSCALL_ERROR;
    }
}
/*Z 设置指定范围的I/O端口位图，set为真时置1，为假时置0 */
void setIOPortMask(void *ioport_bitmap, uint16_t low, uint16_t high, bool_t set)
{
    //get an aliasing pointer
    word_t_may_alias *bitmap = ioport_bitmap;

    int low_word = low >> wordRadix;
    int high_word = high >> wordRadix;
    int low_index = low & MASK(wordRadix);
    int high_index = high & MASK(wordRadix);

    // see if we are just manipulating bits inside a single word. handling this
    // specially makes reasoning easier
    if (low_word == high_word) {
        apply_pattern(bitmap + low_word, make_pattern(low_index, high_index + 1), set);
    } else {
        // operate on the potentially partial first word
        apply_pattern(bitmap + low_word, make_pattern(low_index, CONFIG_WORD_SIZE), set);
        low_word++;
        // iterate over the whole words
        while (low_word < high_word) {
            apply_pattern(bitmap + low_word, ~(word_t)0, set);
            low_word++;
        }
        // apply to any remaining bits
        apply_pattern(bitmap + low_word, make_pattern(0, high_index + 1), set);
    }
}
